package com.gy.hadoop.jvm;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/*
基本结构与之前类似，只是Java8取消了之前的“永久代”，取而代之的是“元空间”——**Metaspace**，两者本质是一样的。“永久代”使用的是JVM的堆内存，而“元空间”是直接使用的本机物理内存。
垃圾判断算法：
    引用计数法：
        重复引用问题
    可达性分析法
        使用GC_ROOT乡下遍历，未达成通路的对象标记为"回收"
        GC_ROOT:
            虚拟机栈（栈帧中的局部表量表）中引用的对象
            本地方法栈（native)中引用的对象
            方法区中常量引用的对象
            方法区中类静态属性引用的对象
### X参数
    用得不多，比如`-Xint`，解释执行模式；`-Xcomp`，编译模式；`-Xmixed`，开启混合模式（默认）。
### XX参数
    重要，用于JVM调优。
        ### 布尔类型
            **公式**：`-XX:+某个属性`、`-XX:-某个属性`，开启或关闭某个功能。比如`-XX:+PrintGCDetails`，开启GC详细信息。
        ### KV键值类型
            **公式**：`-XX:属性key=值value`。比如`-XX:Metaspace=128m`、`-XX:MaxTenuringThreshold=15`。
        ## JVM Xms/Xmx参数
            `-Xms`和`-Xmx`十分常见，用于设置**初始堆大小**和**最大堆大小**。第一眼看上去，既不像X参数，也不像XX参数。实际上`-Xms`等价于`-XX:InitialHeapSize`，`-Xmx`等价于`-XX:MaxHeapSize`。所以`-Xms`和`-Xmx`属于XX参数。
JVM 查看参数
    `jps -l`配合`jinfo -flag JVM参数 pid`  或者 `jps -l`配合`jinfo -flags pid`
    eg：jinfo -flag PrintGCDetails 18052 或者 jinfo -flag 18052
    如果不想查看所有参数，可以用`-XX:+PrintCommandLineFlags`
## JVM 常用参数
    ### -Xmx/-Xms
        最大和初始堆大小。最大默认为物理内存的1/4，初始默认为物理内存的1/64。
    ### -Xss
        等价于`-XX:ThresholdStackSize`。用于设置单个栈的大小，系统默认值是0，**不代表栈大小为0**。而是根据操作系统的不同，有不同的值。比如64位的Linux系统是1024K，而Windows系统依赖于虚拟内存。
    ### -Xmn
        新生代大小，一般不调。
    ### -XX:MetaspaceSize
        设置元空间大小。
    ### -XX:+PrintGCDetails
        输出GC收集信息，包含`GC`和`Full GC`信息。
    ### -XX:SurvivorRatio
        新生代中，`Eden`区和两个`Survivor`区的比例，默认是`8:1:1`。通过`-XX:SurvivorRatio=4`改成`4:1:1`
    ### -XX:NewRatio
        老生代和新年代的比列，默认是2，即老年代占2，新生代占1。如果改成`-XX:NewRatio=4`，则老年代占4，新生代占1。
    ### -XX:MaxTenuringThreshold
        新生代设置进入老年代的时间，默认是新生代逃过15次GC后，进入老年代。如果改成0，那么对象不会在新生代分配，直接进入老年代。
# 四大引用
    以下Demo都需要设置`-Xmx`和`-Xms`，不然系统默认很大，很难演示。
    ## 强引用
        使用`new`方法创造出来的对象，默认都是强引用。GC的时候，就算**内存不够**，抛出`OutOfMemoryError`也不会回收对象，**死了也不回收**。详见[StrongReferenceDemo](https://github.com/MaJesTySA/JVM-JUC-Core/blob/master/src/jvm/StrongReferenceDemo.java)。
    ## 软引用
        需要用`Object.Reference.SoftReference`来显示创建。**如果内存够**，GC的时候**不回收**。**内存不够**，**则回收**。常用于内存敏感的应用，比如高速缓存。详见[SoftReferenceDemo](https://github.com/MaJesTySA/JVM-JUC-Core/blob/master/src/jvm/SoftReferenceDemo.java)。
    ## 弱引用
        需要用`Object.Reference.WeakReference`来显示创建。**无论内存够不够，GC的时候都回收**，也可以用在高速缓存上。详见[WeakReferenceDemo](https://github.com/MaJesTySA/JVM-JUC-Core/blob/master/src/jvm/WeakReferenceDemo.java)
    ### WeakHashMap
        传统的`HashMap`就算`key==null`了，也不会回收键值对。但是如果是`WeakHashMap`，一旦内存不够用时，且`key==null`时，会回收这个键值对。详见[WeakHashMapDemo](https://github.com/MaJesTySA/JVM-JUC-Core/blob/master/src/jvm/WeakHashMapDemo.java)。
    ## 虚引用
        软应用和弱引用可以通过`get()`方法获得对象，但是虚引用不行。虚引形同虚设，在任何时候都可能被GC，不能单独使用，必须配合**引用队列（ReferenceQueue）**来使用。设置虚引用的**唯一目的**，就是在这个对象被回收时，收到一个**通知**以便进行后续操作，有点像`Spring`的后置通知。详见[PhantomReferenceDemo](https://github.com/MaJesTySA/JVM-JUC-Core/blob/master/src/jvm/PhantomReferenceDemo.java)。
    ## 引用队列
        弱引用、虚引用被回收后，会被放到引用队列里面，通过`poll`方法可以得到。关于引用队列和弱、虚引用的配合使用，见[ReferenceQueueDemo](https://github.com/MaJesTySA/JVM-JUC-Core/blob/master/src/jvm/ReferenceQueueDemo.java)。
四大垃圾收集算法
    标记整理：
    标记清除：
    复制算法：
    分代收集算法：根据对象GC年代，采用混合算法来收集
        新生代：每次GC回收对象较多，采用复制算法
        老年代：采用标记整理/标记清除算法
四种垃圾收集器（java8）：
    串行收集器Serial：单线程清理，GC时暂停用户线程，不适用于服务器；
    并行收集器Parrallel：多线程同时GC，GC时会暂停用户线程，适用于科学计算，大数据后台，交互不敏感场景；
    并发收集器CMS：用户线程和GC线程同时执行（不一定是并行，交替执行），GC时不需要停顿用户线程，互联网公司多用
    G1收集器： 对内存的划分与前面3种很大不同，将堆内存分割成不同的区域，然后并发地进行垃圾回收
默认垃圾收集器：`Serial`、`Parallel`、`ConcMarkSweep`（CMS）、`ParNew`、`ParallelOld`、`G1`。还有一个`SerialOld`，快被淘汰了
    查看：java -XX:+PrintCommandLineFlags可以看到叫ava8默认使用`-XX:+UseParallelGC`
体系结构:
    新生代：`Serial`、`Parallel Scavenge`、`ParNew`,`G1`
    老年代：`SerialOld`、`ParallelOld`、`CMS`,`G1`
    搭配：Serial+CMS及ParNew+SerialOld不推荐，
        Serial              -->         SerialOld
        Parallel Scavenge   -->         ParallelOld
        Parallel Scavenge   -->         SerialOld
        ParNew              -->         CMS
        G1                  -->         G1
七大垃圾收集器：
    `Serial`、：复制算法，单线程串行收集，-XX:+UseSerialGC开启（`Serial`+`SerialOld`），
    `ParNew`、：复制算法，多线程串行收集，-XX:+UseParNewGC开启（`ParNew`+`SerialOld`，SerialOld过时，建议配合CMS）
    `Parallel Scavenge`：复制算法，多线程串行收集（吞吐量优先收集器，自适应保证吞吐量）
        -XX:+UseParallelGC开启（`Parallel Scavenge`+`ParallelOld`）
        其它参数：比如`-XX:ParallelGCThreads=N`可以选择N个线程进行GC，`-XX:+UseAdaptiveSizePolicy`使用自适应调节策略
    SerialOld：标整算法，JDK1.5前配合Parallel Scavenge使用，现在基本不用
    ParallelOld：标整算法，-XX:+UseParallelOldGC开启
    CMS：并发标记清除收集器，GC和用户线程并并发执行，标记时会STW
        `-XX:+UseConcMarkSweepGC`开启。开启过后，新生代默认使用`ParNew`，同时老年代使用`SerialOld`作为备用
        #### 过程
            1. **初始标记**：只标记GC Roots直接关联的对象，速度快，需要**STW**。
            2. **并发标记**：主要标记过程，标记全部对象，和用户线程一起工作，不需要STW。
            3. **重新标记**：修正在并发标记阶段出现的变动，需要**STW**。
            4. **并发清除**：和用户线程一起，清除垃圾，不需要STW。
        #### 优缺点
            **优点**：停顿时间少，响应速度快，用户体验好。
        **缺点**：
            1. 对CPU资源非常敏感：由于需要并发工作，多少会占用系统线程资源。
            2. 无法处理浮动垃圾：由于标记垃圾的时候，用户进程仍然在运行，无法有效处理新产生的垃圾。
            3. 产生内存碎片：由于使用**标清算法**，会产生内存碎片。
    G1：G1区和元空间组成，1个个Region既可能时新生代，也可能时老年代。
        `G1`收集器既可以提高吞吐量，又可以减少GC时间。最重要的是**STW可控**，增加了预测机制，让用户指定停顿时间
        `-XX:+UseG1GC`开启，还有`-XX:G1HeapRegionSize=n`、`-XX:MaxGCPauseMillis=n`等参数可调。
        #### 特点
            1. **并行和并发**：充分利用多核、多线程CPU，尽量缩短STW。
            2. **分代收集**：虽然还保留着新、老两代的概念，但物理上不再隔离，而是融合在Region中。
            3. **空间整合**：`G1`整体上看是**标整**算法，在局部看又是**复制算法**，不会产生内存碎片。
            4. **可预测停顿**：用户可以指定一个GC停顿时间，`G1`收集器会尽量满足。
        #### 过程
            与`CMS`类似。
            1. 初始标记。
            2. 并发标记。
            3. 最终标记。
            4. 筛选回收。
Linux相关指令:
    ## top
        主要查看`%CPU`、`%MEM`，还有`load average`。`load average`后面的三个数字，表示系统1分钟、5分钟、15分钟的平均负载值。如果三者平均值高于0.6，则复杂比较高了。当然，用`uptime`也可以查看。
    ## vmstat
        查看进程、内存、I/O等多个系统运行状态。2表示每两秒采样一次，3表示一共采样3次。`procs`的`r`表示运行和等待CPU时间片的进程数，原则上1核CPU不要超过2。`b`是等待资源的进程数，比如磁盘I/O、网络I/O等。
    ```shell
    [root@ ~]# vmstat -n 2 3
    procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
     r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
     2  0      0 173188 239748 1362628    0    0     0     3   17    8  0  0 99  0  0
     0  0      0 172800 239748 1362636    0    0     0     0  194  485  1  1 99  0  0
     1  0      0 172800 239748 1362640    0    0     0     0  192  421  1  1 99  0  0
    ```
    ## pidstat
        查看某个进程的运行信息。
    ## free
        查看内存信息。
    ## df
        查看磁盘信息。
    ## iostat
        查看磁盘I/O信息。比如有时候MySQL在查表的时候，会占用大量磁盘I/O，体现在该指令的`%util`字段很大。对于死循环的程序，CPU占用固然很高，但是磁盘I/O不高。
    ## ifstat
        查看网络I/O信息，需要安装。
    # CPU占用过高原因定位
        先用`top`找到CPU占用最高的进程，然后用`ps -mp pid -o THREAD,tid,time`，得到该**进程**里面占用最高的**线程**。这个线程是10进制的，将其转成16进制，然后用`jstack pid | grep tid`可以定位到具体哪一行导致了占用过高。
# JVM性能调优和监控工具
    ## jps
        Java版的`ps -ef`查看所有JVM进程。
    ## jstack
        查看JVM中运行线程的状态，比较重要。可以定位CPU占用过高位置，定位死锁位置。
    ## jinfo/jstat
        `jinfo`查看JVM的运行环境参数，比如默认的JVM参数等。`jstat`统计信息监视工具。
    ## jmap
        JVM内存映像工具。
 */
public class Main {

    public static void main(String[] args) {

    }

    static class OomCls {

        /**
         * 栈溢出
         * 栈满会抛出该错误。无限递归就会导致StackOverflowError
         */
        private void stackOverflowError() {
            stackOverflowError();
        }

        /**
         * 栈满
         * 栈满会抛出该错误
         */
        //-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
        private void JavaHeapSpace() {
            String str = "adf";
            while (true) {
                str += str + new Random().nextInt(1111111) + new Random().nextInt(222222);
                str.intern();
            }
        }

        /**
         * GC无用异常
         * 这个错误是指：GC的时候会有“Stop the World"，STW越小越好，正常情况是GC只会占到很少一部分时间。但是如果用超过98%的时间来做GC，而且收效甚微，就会被JVM叫停。下例中，执行了多次`Full GC`，但是内存回收很少，最后抛出了`OOM:GC overhead limit exceeded`错误
         */
        //-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
        private void GCOverhead() {
            int i = 0;
            List<String> list = new ArrayList<>();
            try {
                while (true) {
                    list.add(String.valueOf(++i).intern());
                }
            } catch (Exception e) {
                System.out.println("************i" + i);
                e.printStackTrace();
                throw e;
            }
        }

        /**
         * 堆外异常 nio
         * 在写`NIO`程序的时候，会用到`ByteBuffer`来读取和存入数据。与Java堆的数据不一样，`ByteBuffer`使用`native`方法，直接在**堆外分配内存**。当堆外内存（也即本地物理内存）不够时，就会抛出这个异常
         */
        //-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
        private void DirectBufferMemory() {
            System.out.println("配置的maxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");
            try {
                Thread.sleep(300);
            } catch (Exception e) {
                e.printStackTrace();
            }
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
        }

        /**
         * 超过一同最大线程数异常
         * 在高并发应用场景时，如果创建超过了系统默认的最大线程数，就会抛出该异常。Linux单个进程默认不能超过1024个线程
         */
        private void UnableCreateNewThread() {
            for (int i = 0; ; i++) {
                System.out.println("***********" + i);
                new Thread(() -> {
                    try {
                        Thread.sleep(Integer.MAX_VALUE);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }, "" + i).start();
            }
        }

        /**
         * OOM—Metaspace
         * 元空间满了就会抛出这个异常。
         */
        private void OOM_Metaspace() {

        }

    }

}